/**
 * Copyright 2013 Netflix, Inc.
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.servo.publish;

import com.netflix.servo.Metric;
import com.netflix.servo.util.UnmodifiableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Combines results from a list of metric pollers. This clas
 */
public class CompositeMetricPoller implements MetricPoller {

  private static final Logger LOGGER = LoggerFactory.getLogger(CompositeMetricPoller.class);

  private static final List<Metric> EMPTY = UnmodifiableList.of();

  private static final String POLLER_KEY = "PollerName";

  private final Map<String, MetricPoller> pollers;
  private final ExecutorService executor;
  private final long timeout;

  /**
   * Creates a new instance for a set of pollers.
   *
   * @param pollers  a set of pollers to collect data from, the map key for a
   *                 poller is used as a name identify a particular poller
   *                 for logging and error messages
   * @param executor an executor to use for executing the poll methods
   * @param timeout  timeout in milliseconds used when getting the value
   *                 from the future
   */
  public CompositeMetricPoller(
      Map<String, MetricPoller> pollers, ExecutorService executor, long timeout) {
    this.pollers = Collections.unmodifiableMap(pollers);
    this.executor = executor;
    this.timeout = timeout;
  }

  private void increment(Throwable t, String name) {
    //TagList tags = SortedTagList.builder().withTag(new BasicTag(POLLER_KEY, name)).build();
    //Counters.increment(t.getClass().getSimpleName() + "Count", tags);
  }

  private List<Metric> getMetrics(String name, Future<List<Metric>> future) {
    List<Metric> metrics = EMPTY;
    try {
      metrics = future.get(timeout, TimeUnit.MILLISECONDS);
    } catch (ExecutionException e) {
      increment(e, name);
      LOGGER.warn("uncaught exception from poll method for {}", name, e);
    } catch (TimeoutException e) {
      // The cancel is needed to prevent the slow task from using up all threads
      future.cancel(true);
      increment(e, name);
      LOGGER.warn("timeout executing poll method for {}", name, e);
    } catch (InterruptedException e) {
      increment(e, name);
      LOGGER.warn("interrupted while doing get for {}", name, e);
    }
    return metrics;
  }

  /**
   * {@inheritDoc}
   */
  public final List<Metric> poll(MetricFilter filter) {
    return poll(filter, false);
  }

  /**
   * {@inheritDoc}
   */
  public final List<Metric> poll(MetricFilter filter, boolean reset) {
    Map<String, Future<List<Metric>>> futures = new HashMap<>();
    for (Map.Entry<String, MetricPoller> e : pollers.entrySet()) {
      PollCallable task = new PollCallable(e.getValue(), filter, reset);
      futures.put(e.getKey(), executor.submit(task));
    }

    List<Metric> allMetrics = new ArrayList<>();
    for (Map.Entry<String, Future<List<Metric>>> e : futures.entrySet()) {
      allMetrics.addAll(getMetrics(e.getKey(), e.getValue()));
    }
    return allMetrics;
  }
}
